package ar.com.jsejtman.common.service.localization;

import ar.com.jsejtman.common.service.exception.ServiceLocatorException;
import java.util.Map;
import org.springframework.context.ApplicationContext;

/**
 * Implementación de un localizador de servicios usando Spring.
 * 
 * @author jsejtman
 */
public class SpringServiceLocator implements IServiceLocator {

	/**
	 * Contexto de la aplicación a utilizar.
	 */
	private ApplicationContext applicationContext;

	/**
	 * Obtiene un servicio del registro. Para ello, usa el ID del mismo, así como la clase del
	 * servicio a obtener.
	 * 
	 * @param <Service> Tipo de clase de servicio a obtener.
	 * @param serviceId Identificador del servicio (id del bean en Spring).
	 * @param serviceInterface Clase del servicio a obtener.
	 * @return Servicio obtenido.
	 */
	@Override
	public final <Service> Service getService(final String serviceId,
	        final Class<Service> serviceInterface) {

		this.checkContext();

		try {
			return (this.getApplicationContext().getBean(serviceId, serviceInterface));
		} catch (final RuntimeException e) {
			final String serviceInterfaceName =
			        (serviceInterface != null) ? serviceInterface.getName() : "-null-";

			throw new ServiceLocatorException("Error obteniendo el servicio mediante su ID ["
			        + serviceId + "] para la clase [" + serviceInterfaceName + "].", e);
		}
	}

	/**
	 * Método para obtener un servicio que tiene una única implementación en el registro. Es
	 * importante destacar que sólo UN punto de servicio debe implementar el mismo.
	 * 
	 * @param <Service> Tipo de clase de servicio a obtener.
	 * @param serviceInterface La clase a la que debe castear el servicio obtenido.
	 * @return El servicio obtenido.
	 */
	@SuppressWarnings("unchecked")
	public final <Service> Service getService(final Class<Service> serviceInterface) {

		this.checkContext();

		final String serviceInterfaceName =
		        (serviceInterface != null) ? serviceInterface.getName() : "-null-";

		Map<String, Service> beans = null;

		try {
			beans = this.getApplicationContext().getBeansOfType(serviceInterface);
		} catch (final RuntimeException e) {
			throw new ServiceLocatorException("Error obteniendo el servicio mediante su clase ["
			        + serviceInterfaceName + "].", e);
		}

		if (beans.size() > 1) {
			throw new ServiceLocatorException(
			        "Hay mas de un servicio implementado la interfaz indicada ["
			                + serviceInterfaceName + "]. Intente obtenerlo por Service Id.");
		} else if (beans.isEmpty()) {
			throw new ServiceLocatorException(
			        "No hay servicio que implemente la interfaz indicada [" + serviceInterfaceName
			                + "].");
		} else {
			final Object[] array = beans.values().toArray();

			return ((Service) array[0]);
		}
	}

	/**
	 * Asigna el Application Context.
	 * 
	 * @param applicationContext Application Context a asignar.
	 */
	public final void setApplicationContext(final ApplicationContext applicationContext) {

		this.applicationContext = applicationContext;
	}

	/**
	 * Devuelve el Application Context.
	 * 
	 * @return Application Context.
	 */
	public final ApplicationContext getApplicationContext() {

		return (this.applicationContext);
	}

	/**
	 * Verifica que el contexto de la aplicación haya sido inyectado al localizador. Lanza una
	 * excepción en caso contrario.
	 */
	private void checkContext() {

		if (this.getApplicationContext() == null) {
			throw new IllegalStateException("El Application Context no ha sido creado/asignado "
			        + "al localizador de servicios de Spring.");
		}
	}
}
